package sm.upgrade.api.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sm.codec.type.NullValue;
import sm.dbdict.DBDictUtil;
import sm.dbdict.def.DBDict;
import sm.dbdict.def.Field;
import sm.dbdict.def.Key;
import sm.def.KeyType;
import sm.def.Type;
import sm.hibernate.DAMUtil;
import sm.upgrade.api.KeyValidator;
import sm.upgrade.exception.UpgradeException;
import sm.util.SMMap;
import sm.util.Util;

public abstract class AbstractKeyValidator implements KeyValidator {

	private static Logger logger = LoggerFactory.getLogger(KeyValidator.class);

	private Field getCharacterKeyField(List<Field> keyFields) {
		Field chosenField = null;
		for (Field keyField : keyFields) {
			if (keyField.getType() == Type.CHARACTER_TYPE) {
				chosenField = keyField;
				break;
			}
		}
		return chosenField;
	}

	private List<Object> createNewKeyValue(List<Object> keyValue,
			Object chosenValue, Object fixedValue) {
		List<Object> newKeyValue = new ArrayList<Object>();
		for (Object value : keyValue) {
			if (chosenValue.equals(value)) {
				newKeyValue.add(fixedValue);
			} else {
				newKeyValue.add(keyValue);
			}
		}
		return newKeyValue;
	}

	private Object fixNull(int type) throws UpgradeException {
		Object fixedValue = null;
		if (type == Type.NUMBER_TYPE) {
			fixedValue = 0;
		} else if (type == Type.CHARACTER_TYPE) {
			fixedValue = "NotNull_Renamed";
		}
		return fixedValue;
	}

	private List<Object> fixNotNullKey(String fileName, List<Field> keyFields,
			List<Object> keyValue, boolean fix) throws UpgradeException {
		int size = keyFields.size();
		List<Object> newKeyValue = new ArrayList<Object>();
		boolean hasNullValue = false;
		for (int i = 0; i < size; i++) {
			Object value = keyValue.get(i);
			if (value == null || value instanceof NullValue) {
				hasNullValue = true;
				if (fix) {
					// return a fixed key
					Field field = keyFields.get(i);
					int type = field.getType();
					Object fixedValue = fixNull(type);
					if (fixedValue == null) {
						throw new UpgradeException(
								"Can not fix NULL value for dbdict:" + fileName
										+ ",field:"
										+ DBDictUtil.getFieldPath(field));
					}
					newKeyValue.add(fixedValue);
				} else {
					// return an empty key
					newKeyValue = Collections.EMPTY_LIST;
					break;
				}
			} else {
				newKeyValue.add(value);
			}
		}

		if (hasNullValue) {
			return newKeyValue;
		} else {
			return null;
		}
	}

	private List<Object> fixUniqueKey(String fileName, String keyStr,
			SMMap dataMap, String chosenPath, List<Object> keyValue,
			Map<Object, Integer> keyValues, boolean fix)
			throws UpgradeException {
		List<Object> newKeyValue = null;
		Integer count = keyValues.get(keyValue);
		if (count != null) { // unique key conflict
			if (fix) {
				// return a fixed unique key
				if (chosenPath == null) {
					throw new UpgradeException("No character field for dbdict:"
							+ fileName + ",unique key:" + keyStr);
				}

				String fixedValue = "";
				Object chosenValue = dataMap.get(chosenPath);
				if (chosenValue instanceof String) {
					fixedValue = (String) chosenValue;
				}
				if (count == 0) {
					fixedValue = fixedValue + "_Renamed";
				} else {
					fixedValue = fixedValue + "_Renamed_" + count;
				}

				newKeyValue = createNewKeyValue(keyValue, chosenValue,
						fixedValue);
				// check if the fixed key is exist
				if (keyValues.containsKey(newKeyValue)) {
					keyValues.put(keyValue, ++count);
					newKeyValue = fixUniqueKey(fileName, keyStr, dataMap,
							chosenPath, keyValue, keyValues, fix);
				}

				keyValues.put(newKeyValue, 0);
			} else {
				// return an empty key
				newKeyValue = Collections.EMPTY_LIST;
			}
		} else {
			keyValues.put(keyValue, 0);
		}

		return newKeyValue;
	}

	@Override
	public boolean validateKey(DBDict dbdict, Key newKey, List<SMMap> datas,
			boolean fix) throws UpgradeException {
		boolean hasConflict = false;
		String fileName = dbdict.getName();
		int type = newKey.getFlags();
		if (logger.isDebugEnabled()) {
			logger.debug("Check " + KeyType.getType(type).getName() + " key "
					+ DBDictUtil.getKeyString(newKey) + " for table "
					+ fileName);
		}

		String keyStr = DBDictUtil.getKeyString(newKey);
		List<Field> keyFields = DBDictUtil.getKeyFields(newKey,
				dbdict.getField());
		Field chosenField = getCharacterKeyField(keyFields);
		String chosenPath = null;
		if (chosenField != null) {
			chosenPath = Util.radFieldPathToJava(DBDictUtil
					.getFieldPath(chosenField));
		}
		String[] radKeyPaths = DBDictUtil.getKeyPaths(keyFields);
		String[] keyPaths = Util.radFieldPathsToJava(radKeyPaths);

		Map<Object, Integer> keyValues = new HashMap<Object, Integer>();
		for (SMMap dataMap : datas) {
			Object[] keyValue = DAMUtil.getValues(dataMap, keyPaths);
			List<Object> keyValueList = Arrays.asList(keyValue);
			List<Object> newKeyValueList = null;
			if (KeyType.isUniqueKey(type)) {
				newKeyValueList = fixUniqueKey(fileName, keyStr, dataMap,
						chosenPath, keyValueList, keyValues, fix);
			} else if (KeyType.isNotNullKey(type)) {
				newKeyValueList = fixNotNullKey(fileName, keyFields,
						Arrays.asList(keyValue), fix);
			}

			if (newKeyValueList != null) {
				hasConflict = true;
				if (fix) {
					Object[] newKeyValue = newKeyValueList.toArray();
					boolean fixed = resolveConflict(fileName, dataMap, keyStr,
							type, keyPaths, keyValue, newKeyValue);
					if (fixed) {
						break;
					}
				} else {
					logger.error("Conflict for dbdict:" + fileName
							+ ",unique key:" + keyStr + " with value:"
							+ Util.formatKey(keyValue));
				}
			}
		}
		return hasConflict;
	}
}
